
namespace SharpTalk.Client.WpfApplication.Services
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Configuration;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.ServiceModel;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Threading;
    using Client.Services;
    using Core;
    using Core.Base;
    using Core.Delegates;
    using Core.Enums;
    using Core.EventArgs;
    using Core.Interfaces;
    using Core.Services;
    using Core.Services.Requests;
    using Core.Services.Responses;
    using Core.Services.ServiceAgents;
    using Properties;
    using Views;

    /// <summary>
    /// Application interface contract
    /// </summary>
    public interface IApplicationServices
    {
        /// <summary>
        /// Occurs when a property changes
        /// </summary>
        event PropertyChangedEventHandler PropertyChanged;
        /// <summary>
        /// Obtains current user's discussions collection
        /// </summary>
        ObservableCollection<Discussion> Discussions { get; }
        /// <summary>
        /// Obtains  current user's contacts collection
        /// </summary>
        ObservableCollection<User> Contacts { get; }
        /// <summary>
        /// Obtains user's created chatrooms collection
        /// </summary>
        ObservableCollection<ChatRoomDetails> CreatedChatRooms { get; }
        /// <summary>
        /// Obtains Current user
        /// </summary>
        User CurrentUser { get; }
        /// <summary>
        /// Obtains or defines MainView Dispatcher
        /// </summary>
        Dispatcher UI { get; set; }
        /// <summary>
        /// Obtains current Application states
        /// </summary>
        ApplicationServiceStates States { get; }
        /// <summary>
        /// Determines if Application is initialized
        /// </summary>
        bool IsInitialized { get; }
        /// <summary>
        /// Obtains or defines if automaticaly truncated messages should be saved
        /// </summary>
        bool SaveTruncatedMessages { get; set; }
        /// <summary>
        /// Shows a message
        /// </summary>
        /// <param name="message">The text of the message</param>
        /// <param name="isError">Determines if it's an error</param>
        void ShowMessage(string message, bool isError);
        /// <summary>
        /// Finds a chatroom by its name
        /// </summary>
        /// <param name="name">The non case sensitive chatroom name</param>
        /// <returns>A list of chatroom details</returns>
        List<ChatRoomDetails> FindChatRooms(string name);
        /// <summary>
        /// Leaves a chatroom
        /// </summary>
        /// <param name="chatroom">The chatroom to leave</param>
        /// <returns>True if succeeded otherwise false</returns>
        bool LeaveChatRoom(ChatRoomDetails chatroom);
        /// <summary>
        /// Joins a chatroom
        /// </summary>
        /// <param name="chatroom">The chatroom details to join</param>
        /// <param name="password">The password needed if any</param>
        /// <returns>True if succeeded otherwise false</returns>
        bool JoinChatRoom(ChatRoomDetails chatroom, byte[] password);
        /// <summary>
        /// Registers a new user
        /// </summary>
        /// <param name="userDetails">The user details</param>
        /// <returns>True if succeeded otherwise false</returns>
        bool RegisterNewUser(User userDetails);
        /// <summary>
        /// Updates a user's created chatroom details
        /// </summary>
        /// <param name="chatroom">The chatroom details to update</param>
        /// <param name="definePassword">Determines if a password need to be set/updated</param>
        /// <param name="password">The password to set/update</param>
        /// <returns>True if succeeded otherwise false</returns>
        bool UpdateChatRoomDetails(ChatRoomDetails chatroom, bool definePassword, byte[] password);
        /// <summary>
        /// Updates the maximum count of messages to display.
        /// </summary>
        /// <param name="value">A value greater than 0</param>
        void UpdateMaximumDiscussionMessageCount(int value);
        /// <summary>
        /// Creates a new chatroom
        /// </summary>
        /// <param name="chatroom">The chatroom details</param>
        /// <param name="password">The chatroom password if any</param>
        void CreateNewChatRoom(ChatRoomDetails chatroom, byte[] password);
        /// <summary>
        /// Fins a chatroom and try to join it
        /// </summary>
        /// <param name="name">The name of the chatroom</param>
        /// <param name="type">The type of the chatroom</param>
        /// <param name="password">The password to provide if any</param>
        void FindChatRoomAndJoin(string name, ChatRoomTypeEnum type, byte[] password);
        /// <summary>
        /// Exists application
        /// </summary>
        void Exit();
        /// <summary>
        /// Persists current user's data
        /// </summary>
        void SaveData();
        /// <summary>
        /// Obtains or defines default communication timeout
        /// </summary>
        int CommunicationTimeOut { get; set; }
    }


    /// <summary>
    /// 
    /// </summary>
    public sealed class ApplicationServices
        : PropertyChangedBase, IApplicationServices
    {
        /// <summary>
        /// Singleton reference
        /// </summary>
        private static readonly IApplicationServices Instance = new ApplicationServices();

        private const string FILE_ROOMS = "rooms.dat";
        private const string FILE_USER = "user.dat";
        private const string FILE_CONTACTS = "contacts.dat";
        private int _timeout = 5000;
        private readonly string _userConfigDirectory;
        private Client.Services.MessageServices _deliverMessageService;
        private ServiceHost _clientServicehost;
        private bool _initialized;
        private bool _saveTruncatedMessages;
        private readonly ApplicationServiceStates _states = new ApplicationServiceStates();
        private User _currentUser;
        private ObservableCollection<ChatRoomDetails> _createdChatrooms;
        private ObservableCollection<User> _contacts;
        private ObservableCollection<Discussion> _discussions;
        private Dispatcher _ui;
        private readonly Dictionary<string, IMessageLogger> _messageLoggers;
        private MessageHandlers _messageHandlers;
        

        /// <summary>
        /// Obtains the single instance
        /// </summary>
        public static IApplicationServices Current
        {
            get { return Instance ; }
        }
        /// <summary>
        /// Creates a new instance
        /// </summary>
        private ApplicationServices()
        {
            TraceProvider = App.Trace;

            if (string.IsNullOrEmpty(Settings.Default.ServerAddress))
                throw new ArgumentNullException("ServerAddress");

            _states.Initializing = true;
            _saveTruncatedMessages = Settings.Default.SaveMessages;

            _userConfigDirectory = Path.GetDirectoryName(ConfigurationManager.OpenExeConfiguration(
                ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath);

            LoadChatRooms();
            LoadUser();
            LoadContacts();

            _messageLoggers = new Dictionary<string, IMessageLogger>(0);
            _discussions = new ObservableCollection<Discussion>();

            if (_currentUser != null)
                AddUserEventHandlers();

            Task.Factory.StartNew(InitClientServices);

            if (_contacts != null && _contacts.Any())
                Task.Factory.StartNew(() => NotifyOnUserChanges(_contacts));

            InitializeMessageHandlers();
        }
        /// <summary>
        /// Initializes message received handlers
        /// </summary>
        private void InitializeMessageHandlers()
        {
            _messageHandlers = new MessageHandlers();
            _messageHandlers.AddHandler(new SimpleMessageHandler(this.TraceProvider, this));
            _messageHandlers.AddHandler(new UserNotificationMessageHandler(this.TraceProvider, this));
        }
        /// <summary>
        /// 
        /// </summary>
        private void InitClientServices()
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());

            _deliverMessageService = new MessageServices(App.Trace);
            _deliverMessageService.MessageReceived += OnMessageReceived;

            InitServiceHost(false);

            IsInitialized = true;
            _states.Initializing = false;
        }
        /// <summary>
        /// Obtains or define the current <see cref="T:SharpTalk.Core.User"/>.
        /// </summary>
        public User CurrentUser
        {
            get { return _currentUser; }
            set
            {
                RemoveUserEventHandlers();

                _currentUser = value;
                if (_currentUser != null)
                    _currentUser.Refresh();

                AddUserEventHandlers();
                NotifyPropertyChange(() => CurrentUser);
            }
        }
        /// <summary>
        /// 
        /// </summary>
        public bool IsInitialized
        {
            get { return _initialized; }
            private set
            {
                _initialized = value;
                NotifyPropertyChange(() => IsInitialized);
            }
        }
        /// <summary>
        /// Obtains or defines the Discussions collection
        /// </summary>
        public ObservableCollection<Discussion> Discussions
        {
            get { return _discussions; }
            set { _discussions = value; NotifyPropertyChange(() => Discussions); }
        }
        /// <summary>
        /// Obtains the recent chatroom list
        /// </summary>
        public ObservableCollection<ChatRoomDetails> CreatedChatRooms
        {
            get { return _createdChatrooms; }
            private set { _createdChatrooms = value; NotifyPropertyChange(() => CreatedChatRooms); }
        }
        /// <summary>
        /// Obtains or defines the List of user contacts
        /// </summary>
        public ObservableCollection<User> Contacts
        {
            get { return _contacts; }
            private set { _contacts = value; NotifyPropertyChange(() => Contacts); }
        }
        /// <summary>
        /// Obtains or set the Ui Thread
        /// </summary>
        public Dispatcher UI
        {
            get { return _ui; }
            set { _ui = value; }
        }
        /// <summary>
        /// Obtains the current states
        /// </summary>
        public ApplicationServiceStates States
        {
            get { return _states; }
        }
        /// <summary>
        /// Obtains or defines if truncated messages should be saved to disk
        /// </summary>
        public bool SaveTruncatedMessages
        {
            get
            {
                return _saveTruncatedMessages;
            }
            set
            {
                _saveTruncatedMessages = value;
            }
        }
        /// <summary>
        /// Obtains or defines default communication timeout
        /// </summary>
        public int CommunicationTimeOut
        {
            get { return _timeout; }
            set { if(value > 0) _timeout = value; }
        }
        /// <summary>
        /// Save content data
        /// </summary>
        public void SaveData()
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());

            SaveChatRooms();
            SaveUser();
            SaveContacts();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public void Exit()
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            _states.Closing = true;

            if (_clientServicehost != null)
                try
                {
                    _clientServicehost.Close();
                }
                catch (Exception exp)
                {
                    TraceException(MethodBase.GetCurrentMethod(), exp);
                }

            if (_deliverMessageService != null)
                _deliverMessageService.MessageReceived -= OnMessageReceived;

            _deliverMessageService = null;

            if (_messageLoggers.Any())
            {
                foreach (var logger in _messageLoggers)
                    logger.Value.Dispose();
                _messageLoggers.Clear();
            }

            _messageHandlers.Clear();

            RemoveUserEventHandlers();
            if (_currentUser == null) return;

            var roomsToLeave = Discussions.Select(d => d.Chatroom).ToArray();
            if (roomsToLeave.Any())
                for (int i = 0; i < roomsToLeave.Length; i++)
                    try
                    {
                        LeaveChatRoom(roomsToLeave[i]);
                    }
                    catch (Exception exp)
                    {
                        TraceException(MethodBase.GetCurrentMethod(), exp);
                    }

            if (_currentUser.Status != UserStatusEnum.Offline)
            {
                _currentUser.Status = UserStatusEnum.Offline;

                var agent = CreateUserServicesAgent();
                try
                {
                    var ar = agent.BeginUpdate(new UserUpdateRequest(_currentUser.Guid) { Content = new UserUpdateRequestDetails() { User = _currentUser } }, null, null);
                    ar.AsyncWaitHandle.WaitOne(_timeout);
                    ar.AsyncWaitHandle.Close();
                    agent.EndUpdate(ar);
                }
                catch (Exception exp)
                {
                    TraceException(MethodBase.GetCurrentMethod(), exp);
                }
                finally
                {
                    if(agent.State != CommunicationState.Faulted)
                        agent.Close();
                }
            }
        }
        /// <summary>
        /// Creates a new <see cref="T:SharpTalk.Core.User"/>.
        /// </summary>
        /// <param name="userDetails">User content details</param>
        /// <returns>True if success otherwise False</returns>
        public bool RegisterNewUser(User userDetails)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            if (!_initialized || userDetails == null) return false;

            if (CurrentUser != null && CurrentUser.Guid != userDetails.Guid)
            {
                _states.DeletingUser = true;

                // Delete Current User
                var delRequest = new UserDeleteRequest(CurrentUser.Guid)
                {
                    Content = new UserDeleteRequestDetails() { Guid = CurrentUser.Guid }
                };

                UserDeleteResponse deleteResponse;
                var agent = CreateUserServicesAgent();
                try
                {
                    var delAr = agent.BeginDelete(delRequest, null, null);
                    delAr.AsyncWaitHandle.WaitOne(_timeout);
                    delAr.AsyncWaitHandle.Close();
                    deleteResponse = agent.EndDelete(delAr);
                }
                catch (Exception exp)
                {
                    deleteResponse = new UserDeleteResponse() { Error = new ErrorDetails(exp) };
                }
                finally
                {
                    if (agent.State != CommunicationState.Faulted)
                        agent.Close();
                }

                _states.DeletingUser = false;

                if (deleteResponse.HasError)
                {
                    ShowErrorMessage(deleteResponse.Error);
                    return false;
                }
            }

            _states.CreatingUser = true;
            var request = new UserCreateRequest()
            {
                Content = new UserCreateRequestDetails()
                {
                    FirstName = userDetails.FirstName,
                    LastName = userDetails.LastName,
                    NickName = userDetails.NickName,
                    Image = userDetails.Image,
                    Uri = Settings.Default.LocalServiceUri
                }
            };

            UserCreateResponse createResponse;
            var createAgent = CreateUserServicesAgent();
            try
            {
                var ar = createAgent.BeginCreate(request, null, null);
                ar.AsyncWaitHandle.WaitOne(_timeout);
                ar.AsyncWaitHandle.Close();
                createResponse = createAgent.EndCreate(ar);
            }
            catch (Exception exp)
            {
                createResponse = new UserCreateResponse() { Error = new ErrorDetails(exp) };
            }
            finally
            {
                if (createAgent.State != CommunicationState.Faulted)
                    createAgent.Close();
            }
            _states.CreatingUser = false;

            if (!createResponse.HasError)
                CurrentUser = createResponse.Content;
            else
                ShowErrorMessage(Resources.Messages.error_registration_failed, createResponse.Error);

            return !createResponse.HasError;
        }
        /// <summary>
        /// Creates a new ChatRoom
        /// </summary>
        /// <param name="chatroom">ChatRoom details</param>
        /// <param name="password"></param>
        /// <returns>True is success otherwise False</returns>
        public void CreateNewChatRoom(ChatRoomDetails chatroom, byte[] password)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            if (!_initialized || CurrentUser == null) return;
            _states.CreatingRoom = true;
            var request = new ChatRoomCreateRequest(CurrentUser.Guid)
            {
                Content = new ChatRoomCreateRequestDetails()
                {
                    Name = chatroom.Name,
                    Type = chatroom.Type,
                    Image = chatroom.Image,
                    PasswordHash = password,
                    Motd = chatroom.Motd
                }
            };

            ChatRoomCreateResponse response;
            var agent = CreateChatRoomServicesAgent();
            try
            {
                var ar = agent.BeginCreate(request, null, null);
                ar.AsyncWaitHandle.WaitOne(_timeout);
                ar.AsyncWaitHandle.Close();
                response = agent.EndCreate(ar);
            }
            catch (Exception exp)
            {
                response = new ChatRoomCreateResponse() { Error = new ErrorDetails(exp) };
            }
            finally
            {
                if (agent.State != CommunicationState.Faulted)
                    agent.Close();
            }

            _states.CreatingRoom = false;

            if (!response.HasError)
            {
                chatroom.Guid = response.Content;
                chatroom.Owner = CurrentUser.Guid;

                if (_ui.CheckAccess())
                    AddToCreatedChatRooms(chatroom);
                else
                    _ui.Invoke(new ActionDelegate<ChatRoomDetails>(AddToCreatedChatRooms), chatroom);
            }
            else
            {
                ShowErrorMessage(response.Error);
                return;
            }

            if (!response.HasError)
                JoinChatRoom(chatroom, password);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="chatroom"></param>
        /// <param name="definePassword"></param>
        /// <param name="password"></param>
        public bool UpdateChatRoomDetails(ChatRoomDetails chatroom, bool definePassword, byte[] password)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            if (chatroom == null) return false;

            _states.UpdatingRoom = true;

            var request = new ChatRoomUpdateRequest(CurrentUser.Guid)
            {
                Content = new ChatRoomUpdateRequestDetails()
                {
                    Guid = chatroom.Guid,
                    Image = chatroom.Image,
                    Motd = chatroom.Motd,
                    Type = chatroom.Type,
                    DefinePassword = definePassword,
                    PasswordHash = password
                }
            };

            ChatRoomUpdateResponse response;
            var agent = CreateChatRoomServicesAgent();
            try
            {
                var ar = agent.BeginUpdate(request, null, null);
                ar.AsyncWaitHandle.WaitOne(_timeout);
                ar.AsyncWaitHandle.Close();
                response = agent.EndUpdate(ar);

            }
            catch (Exception exp)
            {
                response = new ChatRoomUpdateResponse() { Error = new ErrorDetails(exp) };
            }
            finally
            {
                if (agent.State != CommunicationState.Faulted)
                    agent.Close();
            }

            if (response.HasError)
            {
                ShowErrorMessage(response.Error);
            }

            // Updates chatroom even if an error happened. Response contains current chatroom state
            if (_ui.CheckAccess())
                UpdateChatRoom(response.Content);
            else
                _ui.Invoke(new ActionDelegate<ChatRoomDetails>(UpdateChatRoom), response.Content);

            _states.UpdatingRoom = false;
            return !response.HasError;
        }
        /// <summary>
        /// Finds a chatroom by its name and type and try to join it
        /// </summary>
        /// <param name="name">The chat room name.</param>
        /// <param name="type">The type of the chatroom.</param>
        /// <param name="password">The password to provide</param>
        public void FindChatRoomAndJoin(string name, ChatRoomTypeEnum type, byte[] password)
        {
            var result = FindChatRooms(name, type);
            if (result.Any() && result[0]!=null)
                JoinChatRoom(result[0], password);
            else
                ShowErrorMessage(new ErrorDetails(Resources.Messages.message_no_chatroom_found));
        }
        /// <summary>
        /// Finds all Public ChatRooms with a certain name.
        /// </summary>
        /// <param name="name">The name of the ChatRoom</param>
        /// <returns>A list of ChatRoomDetails</returns>
        public List<ChatRoomDetails> FindChatRooms(string name)
        {
            return FindChatRooms(name, ChatRoomTypeEnum.Public);
        }
        /// <summary>
        /// Finds all ChatRooms of a certain type with a certain name.
        /// </summary>
        /// <param name="name">The name of the ChatRoom to find.</param>
        /// <param name="type">The ChatRoomTypeEnum value</param>
        /// <returns>A list of ChatRoomDetails</returns>
        public List<ChatRoomDetails> FindChatRooms(string name,  ChatRoomTypeEnum type)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            var result = new List<ChatRoomDetails>();

            if (!_initialized || CurrentUser == null) return result;

            _states.FindingRoom = true;

            var request = new ChatRoomFindRequest(CurrentUser.Guid)
            {
                Content = new ChatRoomFindRequestDetails()
                {
                    Name = name,
                    Type = type
                }
            };
            ChatRoomFindResponse response;
            var agent = CreateChatRoomServicesAgent();
            try
            {
                var ar = agent.BeginFind(request, null, null);
                ar.AsyncWaitHandle.WaitOne(_timeout);
                ar.AsyncWaitHandle.Close();
                response = agent.EndFind(ar);
            }
            catch (Exception exp)
            {
                response = new ChatRoomFindResponse() { Error = new ErrorDetails(exp) };
            }
            finally
            {
                if (agent.State != CommunicationState.Faulted)
                    agent.Close();
            }

            _states.FindingRoom = false;
            return response.Content;
        }
        /// <summary>
        /// Joins a chatroom
        /// </summary>
        /// <param name="chatroom">The chatroom details to join</param>
        /// <param name="password"></param>
        /// <returns>True if success otherwise False</returns>
        public bool JoinChatRoom(ChatRoomDetails chatroom, byte[] password)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            if (chatroom == null || chatroom.AlreadyJoined()) return false;

            _states.JoiningRoom = true;
            var request = new ChatRoomJoinRequest(CurrentUser.Guid)
            {
                Content = new ChatRoomJoinRequestDetails()
                {
                    Guid = chatroom.Guid,
                    PasswordHash = password
                }
            };

            ChatRoomJoinResponse response;
            var agent = CreateChatRoomServicesAgent();

            try
            {
                var ar = agent.BeginJoinChatRoom(request, null, null);
                ar.AsyncWaitHandle.WaitOne(_timeout);
                ar.AsyncWaitHandle.Close();
                response = agent.EndJoinChatRoom(ar);
            }
            catch (Exception exp)
            {
                response = new ChatRoomJoinResponse() { Error = new ErrorDetails(exp) };
            }
            finally
            {
                if (agent.State != CommunicationState.Faulted)
                    agent.Close();
            }

            if (response.HasError)
                ShowErrorMessage(Resources.Messages.error_join_failed, response.Error);
            else
            {
                if (response.Content != null)
                {
                    var discussion = CreateNewDiscussion(chatroom);
                    Task.Factory.StartNew(() => GetChatRoomUserList(discussion));
                }
            }

            _states.JoiningRoom = false;
            return !response.HasError;
        }
        /// <summary>
        /// Leaves a chatroom
        /// </summary>
        /// <param name="chatroom">The chatroom details to leave</param>
        /// <returns>True if success otherwise False</returns>
        public bool LeaveChatRoom(ChatRoomDetails chatroom)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            if (chatroom == null || !chatroom.AlreadyJoined()) return false;
            _states.LeavingRoom = true;
            var request = new ChatRoomLeaveRequest(CurrentUser.Guid)
            {
                Content = new ChatRoomLeaveRequestDetails()
                {
                    Guid = chatroom.Guid
                }
            };

            ChatRoomLeaveResponse response;
            var agent = CreateChatRoomServicesAgent();
            try
            {
                var ar = agent.BeginLeaveChatRoom(request, null, null);
                ar.AsyncWaitHandle.WaitOne(_timeout);
                ar.AsyncWaitHandle.Close();
                response = agent.EndLeaveChatRoom(ar);
            }
            catch (Exception exp)
            {
                response = new ChatRoomLeaveResponse() { Error = new ErrorDetails(exp) };
            }
            finally
            {
                if (agent.State != CommunicationState.Faulted)
                    agent.Close();
            }

            if (!response.HasError)
            {
                if (response.Content)
                    DeleteDiscussion(chatroom);
            }
            else
                if (!_states.Closing) ShowErrorMessage(Resources.Messages.error_leave_failed, response.Error);

            _states.LeavingRoom = false;
            return !response.HasError;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        public bool NotifyOnUserChanges(User user)
        {
            return NotifyOnUserChanges(new[] { user });
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="users"></param>
        /// <returns></returns>
        public bool NotifyOnUserChanges(IEnumerable<User> users)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            if (CurrentUser == null || users == null || !users.Any()) return false;

            var request = new UserNotifyChangedRequest(CurrentUser.Guid)
            {
                Content = new UserNotifyChangeRequestDetails()
                {
                    Guids = users.Select(user => user.Guid).ToList()
                }
            };

            UserNotifyChangedResponse response;
            var agent = CreateUserServicesAgent();

            try
            {
                var ar = agent.BeginNotifyUserChange(request, null, null);
                ar.AsyncWaitHandle.WaitOne(_timeout);
                ar.AsyncWaitHandle.Close();
                response = agent.EndNotifyUserChange(ar);
            }
            catch (Exception exp)
            {
                response = new UserNotifyChangedResponse() { Error = new ErrorDetails(exp) };
            }
            finally
            {
                if (agent.State != CommunicationState.Faulted)
                    agent.Close();
            }

            if (response.HasError)
                ShowErrorMessage(response.Error);

            return !response.HasError && response.Content;
        }
        /// <summary>
        /// Updates the maximum messages count a discussion stores
        /// </summary>
        public void UpdateMaximumDiscussionMessageCount(int value)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            foreach (var discussion in Discussions)
            {
                discussion.Messages.MaxItems = value;
                discussion.Messages.TrimExcess();
            }
        }
        /// <summary>
        /// Registers with Current <see cref="T:SharpTalk.Core.User"/> NotifyPropertyChanged event.
        /// </summary>
        private void AddUserEventHandlers()
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            if (_currentUser != null)
                _currentUser.PropertyChanged += OnCurrentUserPropertyChanged;
        }
        /// <summary>
        /// Unregisters with Current <see cref="T:SharpTalk.Core.User"/> NotifyPropertyChanged event.
        /// </summary>
        private void RemoveUserEventHandlers()
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            if (_currentUser != null)
                _currentUser.PropertyChanged -= OnCurrentUserPropertyChanged;
        }
        /// <summary>
        /// Initializes the ServiceHost
        /// </summary>
        /// <param name="abort">True to abort current channel state</param>
        private void InitServiceHost(bool abort)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());

            if (_clientServicehost != null)
            {
                if (abort)
                    _clientServicehost.Abort();

                _clientServicehost.Close();
            }
            if (CurrentUser == null) return;

            _clientServicehost = new ServiceHost(_deliverMessageService, new Uri(CurrentUser.Uri));
            try
            {
                _clientServicehost.Open();
                TraceInfo("User {0:N} listening on URI {1}", CurrentUser.Guid, CurrentUser.Uri);
            }
            catch (Exception exp)
            {
                _clientServicehost = null;
                ShowErrorMessage(exp.Message);
            }
        }
        /// <summary>
        /// 
        /// </summary>
        private void LoadChatRooms()
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            var data = Serializer.Load<ChatRoomDetails[]>(_userConfigDirectory + "\\" + FILE_ROOMS);
            _createdChatrooms = data != null
                                 ? new ObservableCollection<ChatRoomDetails>(data)
                                 : new ObservableCollection<ChatRoomDetails>();
        }
        /// <summary>
        /// 
        /// </summary>
        private void SaveChatRooms()
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            if (_createdChatrooms != null && _createdChatrooms.Any())
                Serializer.Save(_userConfigDirectory + "\\" + FILE_ROOMS, _createdChatrooms.ToArray());
        }
        /// <summary>
        /// 
        /// </summary>
        private void LoadUser()
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            _currentUser = Serializer.Load<User>(_userConfigDirectory + "\\" + FILE_USER);
            if (_currentUser == null) return;

            _currentUser.IsNotifying = false;
            _currentUser.IsUpdating = false;
            _currentUser.Status = UserStatusEnum.Offline;
            _currentUser.IsNotifying = true;
        }
        /// <summary>
        /// 
        /// </summary>
        private void SaveUser()
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());

            Serializer.Save(_userConfigDirectory + "\\" + FILE_USER, _currentUser);
        }
        /// <summary>
        /// 
        /// </summary>
        private void LoadContacts()
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            var data = Serializer.Load<User[]>(_userConfigDirectory + "\\" + FILE_CONTACTS);
            _contacts = data != null
                                 ? new ObservableCollection<User>(data)
                                 : new ObservableCollection<User>();
        }
        /// <summary>
        /// 
        /// </summary>
        private void SaveContacts()
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            if (_contacts != null && _contacts.Any())
                Serializer.Save(_userConfigDirectory + "\\" + FILE_CONTACTS, _contacts.ToArray());
        }
        /// <summary>
        /// Obtains the application main window
        /// </summary>
        /// <returns></returns>
        private static Window GetMainWindow()
        {
            return Application.Current.MainWindow;
        }
        /// <summary>
        /// Displays an error dialog message
        /// </summary>
        /// <param name="error"></param>
        private void ShowErrorMessage(ErrorDetails error)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            if (error == null) return;
            ShowMessage(error.Message,true);
        }
        /// <summary>
        /// Displays an error dialog message
        /// </summary>
        /// <param name="message"></param>
        /// <param name="error"></param>
        private void ShowErrorMessage(string message, ErrorDetails error = null)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            if (string.IsNullOrEmpty(message) && error == null) return;
            if (error == null) return;

            var msg = string.IsNullOrEmpty(message)
                          ? error.Message
                          : String.IsNullOrEmpty(error.Message) ? message : String.Format("{0}\n\n{1}", message, error.Message);

            ShowMessage(msg, true);
        }
        /// <summary>
        /// Displays a message to user
        /// </summary>
        /// <param name="message"></param>
        /// <param name="isError"></param>
        public void ShowMessage(string message, bool isError)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            if (_ui.CheckAccess())
                DisplayMessageService.ShowMessage((WindowBase)Application.Current.MainWindow, message, isError, false);
            else
            {
                var wnd = _ui.Invoke(new ActionReturnDelegate<Window>(GetMainWindow), null);
                _ui.Invoke(new Delegates.ShowMessageDelegate(DisplayMessageService.ShowMessage),
                                     new object[] { wnd, message, isError, false });
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private IAsyncResult BeginUpdateUser()
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            _states.UpdatingUser = true;
            _currentUser.IsUpdating = true;
            var request = new UserUpdateRequest(_currentUser.Guid)
            {
                Content = new UserUpdateRequestDetails()
                {
                    User = _currentUser
                }
            };

            var agent = CreateUserServicesAgent();
            return agent.BeginUpdate(request, new AsyncCallback(EndUpdateUser), agent);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="result"></param>
        private void EndUpdateUser(IAsyncResult result)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            UserUpdateResponse response;

            var agent = ((UserServicesAgent)result.AsyncState);
            try
            {
                result.AsyncWaitHandle.Close();
                response = agent.EndUpdate(result);
            }
            catch (Exception exp)
            {
                response = new UserUpdateResponse() { Error = new ErrorDetails(exp) };
            }
            
            if (agent.State != CommunicationState.Faulted)
                agent.Close();
            else
            {
                // Communication with server failed

                InitServiceHost(true);
                _states.UpdatingUser = false;
                _currentUser.IsUpdating = false;

                if(!_states.Closing)
                {
                    NotifyPropertyChange(() => CurrentUser);
                    ShowErrorMessage(response.Error);
                    return;
                }
            }

            TraceDebug("User Update {0}", response.HasError ? "failed." : "succeeded.");

            if (response.HasError && !_states.Closing)
            {
                TraceError(response.Error.Message);

                TraceDebug("Reverting user changes");
                _currentUser = response.Content;

                if (_currentUser != null)
                {
                    Settings.Default.LocalServiceUri = _currentUser.Uri;
                    Settings.Default.Save();
                }

                if (!_states.Closing)
                    ShowErrorMessage(response.Error);
            }

            // reinitialize message service host
            if (!_states.Closing)
                Task.Factory.StartNew(() => InitServiceHost(true));

            if (_currentUser != null)
                _currentUser.IsUpdating = false;

            _states.UpdatingUser = false;

            if (_currentUser == null && !_states.Closing)
            {
                if (_ui.CheckAccess())
                    CreatedChatRooms.Clear();
                else
                    _ui.Invoke(new ActionDelegate(CreatedChatRooms.Clear));

                NotifyPropertyChange(() => CreatedChatRooms);
            }

            if (!_states.Closing)
                NotifyPropertyChange(() => CurrentUser);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="chatroom"></param>
        private void UpdateChatRoom(ChatRoomDetails chatroom)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            if (chatroom == null) return;

            if (_createdChatrooms.Contains(chatroom))
            {
                var croom = _createdChatrooms[_createdChatrooms.IndexOf(chatroom)];
                croom.Image = chatroom.Image;
                croom.Motd = chatroom.Motd;
                croom.Type = chatroom.Type;
            }

            if (!_discussions.Where(d => d.Chatroom == chatroom).Any()) return;
            foreach (var discussion in _discussions.Where(d => d.Chatroom == chatroom))
            {
                discussion.Chatroom.Image = chatroom.Image;
                discussion.Chatroom.Type = chatroom.Type;
                discussion.Chatroom.Motd = chatroom.Motd;
            }

        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="discussion"></param>
        private void GetChatRoomUserList(Discussion discussion)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            if (discussion == null || discussion.Chatroom == null) return;

            var request = new ChatRoomUserListRequest(CurrentUser.Guid)
            {
                Content = new ChatRoomUserListRequestDetails()
                {
                    Guid = discussion.Chatroom.Guid
                }
            };
            ChatRoomUserListResponse response;
            using (var agent = CreateChatRoomServicesAgent())
            {
                var ar = agent.BeginGetUserList(request, null, null);
                ar.AsyncWaitHandle.WaitOne();
                ar.AsyncWaitHandle.Close();
                response = agent.EndGetUserList(ar);
            }

            if (response.HasError)
                ShowErrorMessage(response.Error);
            else
            {
                var users = response.Content;
                var userRequest = new UserFindRequest(CurrentUser.Guid)
                {
                    Content = new UserFindRequestDetails()
                };

                foreach (var guid in users.Where(guid => guid != CurrentUser.Guid))
                {
                    userRequest.Content.Guid = guid;

                    UserFindResponse userResponse;
                    using (var agent = CreateUserServicesAgent())
                    {
                        var ar = agent.BeginFind(userRequest, null, null);
                        ar.AsyncWaitHandle.WaitOne();
                        ar.AsyncWaitHandle.Close();
                        userResponse = agent.EndFind(ar);
                    }

                    if (userResponse.HasError || userResponse.Content == null || !userResponse.Content.Any())
                        continue;
                    var user = userResponse.Content[0];
                    if (user == null) continue;

                    if (_ui.CheckAccess())
                        discussion.Users.Add(user);
                    else
                        _ui.Invoke(new ActionDelegate<User>(discussion.Users.Add), user);
                }
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="chatroom"></param>
        private Discussion CreateNewDiscussion(ChatRoomDetails chatroom)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            if (_discussions.Where(d => d.Chatroom == chatroom).Any()) return null;

            if (_createdChatrooms.Contains(chatroom))
                chatroom.Owner = _createdChatrooms[_createdChatrooms.IndexOf(chatroom)].Owner;

            var discussion = new Discussion(chatroom, Settings.Default.MaximumMessages);

            if (_ui.CheckAccess())
                _discussions.Add(discussion);
            else
                _ui.Invoke(new ActionDelegate<Discussion>(_discussions.Add), discussion);

            discussion.PropertyChanged += OnDiscussionPropertyChanged;
            discussion.SimpleMessageTruncated += OnMessageTruncated;

            if (!_messageLoggers.ContainsKey(chatroom.Name))
                _messageLoggers.Add(chatroom.Name, new MessageLogger(string.Format(".\\{0}.log", chatroom.Name)));

            return discussion;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="message"></param>
        /// <param name="discussion"></param>
        private void SendMessage(SimpleMessage message, Discussion discussion)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            _states.SendingMessage = true;

            var request = new MessageSendRequest(CurrentUser.Guid)
            {
                Content = new MessageSendRequestDetails()
                {
                    ChatRoom = discussion.Chatroom.Guid,
                    Message = message,
                    Type = MessageTypeEnum.UserMessage
                }
            };

            MessageSendResponse response;
            using (var agent = CreateMessageServicesAgent())
            {
                var ar = agent.BeginSendMessage(request, null, null);
                ar.AsyncWaitHandle.WaitOne();
                ar.AsyncWaitHandle.Close();
                response = agent.EndSendMessage(ar);
            }

            if (response.HasError)
                ShowErrorMessage(response.Error);

            _states.SendingMessage = false;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="chatroom"></param>
        private void DeleteDiscussion(ChatRoomDetails chatroom)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            if (_discussions == null) return;
            var item = _discussions.Where(d => d.Chatroom == chatroom).FirstOrDefault();
            if (item == null) return;
            DeleteDiscussion(item);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="discussion"></param>
        private void DeleteDiscussion(Discussion discussion)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            if (_discussions == null || discussion == null) return;

            discussion.PropertyChanged -= OnDiscussionPropertyChanged;
            discussion.SimpleMessageTruncated -= OnMessageTruncated;

            if (_ui.CheckAccess())
                _discussions.Remove(discussion);
            else
                _ui.Invoke(new ActionDelegate<bool, Discussion>(_discussions.Remove), discussion);

            if (!_messageLoggers.ContainsKey(discussion.Chatroom.Name)) return;

            _messageLoggers[discussion.Chatroom.Name].AppendMessages(discussion.Messages);
            _messageLoggers[discussion.Chatroom.Name].Dispose();
            _messageLoggers.Remove(discussion.Chatroom.Name);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="chatroom"></param>
        private void AddToCreatedChatRooms(ChatRoomDetails chatroom)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            if (chatroom == null) return;

            if (!CreatedChatRooms.Contains(chatroom))
                CreatedChatRooms.Add(chatroom);
        }
        /// <summary>
        /// Creates a new UserServicesAgent instance
        /// </summary>
        /// <returns></returns>
        private static UserServicesAgent CreateUserServicesAgent()
        {
            return new UserServicesAgent("UserServiceEndpoint",
                                         new EndpointAddress(
                                             string.Format("http://{0}:8080/SharpTalkServices/UserServices",
                                                           Settings.Default.ServerAddress)));
        }
        /// <summary>
        ///  Creates a new ChatRoomServicesAgent instance
        /// </summary>
        /// <returns></returns>
        private static ChatRoomServicesAgent CreateChatRoomServicesAgent()
        {
            return new ChatRoomServicesAgent("ChatRoomServiceEndpoint",
                                             new EndpointAddress(
                                                 string.Format("http://{0}:8081/SharpTalkServices/ChatRoomServices",
                                                               Settings.Default.ServerAddress)));
        }
        /// <summary>
        ///  Creates a new MessageServiceAgent instance
        /// </summary>
        /// <returns></returns>
        private static MessageServiceAgent CreateMessageServicesAgent()
        {
            return new MessageServiceAgent("MessageServiceEndpoint",
                                           new EndpointAddress(
                                               string.Format("http://{0}:8082/SharpTalkServices/MessageServices",
                                                             Settings.Default.ServerAddress)));
        }
        /// <summary>
        /// Current <see cref="T:SharpTalk.Core.User"/> NotifyPropertyChanged event handler.
        /// </summary>
        private void OnCurrentUserPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());

            if (!_initialized ||
                string.Equals("IsUpdating", e.PropertyName, StringComparison.InvariantCultureIgnoreCase))
                return;

            BeginUpdateUser();
        }
        /// <summary>
        /// Discussion.PropertyChanged event handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnDiscussionPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            if (!string.Equals("CurrentText", e.PropertyName, StringComparison.InvariantCultureIgnoreCase))
                return;
            var discussion = sender as Discussion;
            if (discussion == null) return;

            var text = discussion.CurrentText;
            if (string.IsNullOrEmpty(text)) return;

            text = text.Replace(Environment.NewLine, null);
            if (string.IsNullOrEmpty(text)) return;

            var msg = new SimpleMessage() { Content = String.Format("{0} : {1}", CurrentUser.FullName, text), Owner = CurrentUser.Guid };

            Task.Factory.StartNew(() => SendMessage(msg, discussion));

            if (_ui.CheckAccess())
                discussion.AddMessage(msg);
            else
                _ui.Invoke(new ActionDelegate<SimpleMessage>(discussion.AddMessage), msg);

            discussion.CurrentText = null;
        }
        /// <summary>
        /// Saves truncated message
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnMessageTruncated(object sender, SimpleMessageTruncatedEventArgs e)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            if (!_saveTruncatedMessages || !e.Item.IsNotMotd || _states.Closing) return;

            var logger = _messageLoggers.ContainsKey(e.ChatRoom.Name)
                ? _messageLoggers[e.ChatRoom.Name]
                : null;
            if (logger == null) return;

            logger.AppendMessage(e.Item);
        }
        /// <summary>
        /// MessageServices.MessageReceived EventHandler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnMessageReceived(object sender, MessageEventArgs e)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            if (e == null || e.Details == null || _states.Closing) return;

            if(!_messageHandlers.HasHandler(e.Details.Type))
            {
                TraceInfo("No handler found for message type {0}.", e.Details.Type);
                return;
            }

            var args = new MessageHandleEventArgs();
            try
            {
                _messageHandlers.HandleMessage(e.Details, args);
                if (!args.Handled)
                    TraceError("Handling of message {0} failed.", e.Details.Message);
            }
            catch (Exception exp)
            {
                ShowErrorMessage(new ErrorDetails(exp));
            }
        }
    }
}